Retrofit + RxJava 网络请求的简单封装

Rtrofit 和 RxJava 这两年可是大为火热,基本上新的 APP 都会使用这两个东西。如果作为一个 Android 开发者还不知道有这些东西,那你就真的 OUT 了。

简单介绍

Retrofit

说到 Retrofit 就要说 OkHttp 。因为 Retrofit 是对 OkHttp 的封装。

OkHttp

说到 OkHttp , 又要说到 Android 的几种 Http 请求方式。

  • HttpClint : 官方提供的 Http 请求方式,但是在 Android 5.0 之后,官方就废弃了,而且从系统中移除了,这里就不在讨论了。我想,大家一开始都是从这里开始学 Android 的网络请求的吧。

  • HttpUrConnection : 官方提供的 Http 请求方式,但是不好用。(其实,我也不知道哪里不好,但是大家都说不好用。。。)

  • OkHttp :由 square 团队开发,据说在底层的实现也是自成一派。具有高效的请求效率。

OkHttp 虽然好用,但是在日常的使用中,需要更多频繁和繁琐的操作,这个时候就需要封装一下了。Retrofit 这个时候就顺势登场了。

Retrofit 最具有特色的就是,把请求的接口实例成了 Java 中的接口,还有,就是有很多的注解,可以很方便的实现某些功能。

但是,本文不是介绍 Rtrofit ,这里就不多做介绍。

RxJava

一个异步处理的库。很牛逼。

这个库可以把异步的操作,写成链式的代码,这样看起来就清晰了很多。也提供了多种操作符,让我们使用起来很舒服。

下面贴一个链接,可谓是 RxJava 入门的必看之作。

http://gank.io/post/560e15be2dca930e00da1083?winzoom=1
给 Android 开发者的 RxJava 详解

正式开始

本文的大部分封装灵感来自下面这篇文章

http://gank.io/post/56e80c2c677659311bed9841
RxJava 与 Retrofit 结合的最佳实践

一般后台都会返回类似下面的 json

{
 "resultCode": 0,
 "resultMessage": "成功",
 "data": {}
}

然后一般的网络请求是这样的

先用 Retrofit 写一个请求接口

public interface MovieService {
    @GET("top250")
    Observable<MovieEntity> getTopMovie(@Query("start") int start, @Query("count") int count);
}
//进行网络请求
private void getMovie(){
    //豆瓣电影的接口
    String baseUrl = "https://api.douban.com/v2/movie/";

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();

    MovieService movieService = retrofit.create(MovieService.class);

    movieService.getTopMovie(0, 10)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<MovieEntity>() {
                @Override
                public void onCompleted() {
                    Toast.makeText(MainActivity.this, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onError(Throwable e) {
                    resultTV.setText(e.getMessage());
                }

                @Override
                public void onNext(MovieEntity movieEntity) {
                    resultTV.setText(movieEntity.toString());
                }
            });
}

Wa , 一个网络请求要这么多代码,那怎么行。

首先分解一下步骤:

  1. 创建 Retrofit (设置一些基本参数,如:baseUrl ,超时时间 等)
  2. 创建 Service (根据接口生成实际的 接口对象)
  3. 创建 Observer (接收接口返回的数据,并处理)
  4. 订阅 (把 Service 和 Observer 关联起来,也就是联通起来)

开始封装

HttpManager

对于同一个 APP 来讲,网络请求的一些基本参数,如:baseUrl ,请求超时的时间的等,基本上是一致的。

而且很多情况下,不需要重复创建 Retrofit 对象,所以只需要一个就行了。

把所有的接口都写在一起,也只需要一个 Service 对象就可以了。

还有就是为了方便调试,我们要加一个拦截器,把接口接收到的数据打印出来。

结合这些需求,我们就用 单例模式 来做

public class HttpManager {

    private final static String BASE_URL="http:// 地址 + 端口";
    //超时时间
    private final static int DEFAULT_TIMEOUT = 60;
    private OkHttpClient.Builder builder;
    private Retrofit rxRetrofit;
    private static HttpManager instance;
    private APIService service;
    private OkHttpClient recordClient;

    public OkHttpClient getRecordClient() {
        return recordClient;
    }

    public Retrofit getRecordRetrofit() {
        return recordRetrofit;
    }

    public Retrofit getRxRetrofit() {
        return rxRetrofit;
    }

    public APIService getService() {
        return service;
    }
    //构造方法私有化
    private HttpManager(){
        if (builder==null) {
            builder = new OkHttpClient.Builder()
                    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
            if (BuildConfig.DEBUG) {
                HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
                logging.setLevel(HttpLoggingInterceptor.Level.BODY);
                builder.addInterceptor(logging);
            }
        }
        if (rxRetrofit==null) {
            rxRetrofit = new Retrofit.Builder()
                    .client(builder.build())
                    .baseUrl(BASE_URL)
                    .addConverterFactory(new NullOnEmptyConverterFactory())
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
        }
        if (recordClient==null){
            recordClient=builder.build();
        }
        if (service==null){
            service=rxRetrofit.create(APIService.class);
        }
    }
    //获取单例对象
    public static HttpManager getInstance(){
        if (instance ==null){
            instance =new HttpManager();
        }
        return instance;
    }
}

好的,很简单,继续

我们知道,Retrofit 是支持类型转换的,在上面我们已经添加了 Gson 的解析器。

但是,问题又来了,每次请求过来的数据都是不一样的,我们总不能,每一个都去写一个 Bean 类吧。

还好,一般都会有一定的数据格式。比如下面:

{
 "resultCode": 0,
 "resultMessage": "成功",
 "data": {}
}

然后解析的对象,大概可以这么写

public class HttpResult<T> {
    private int resultCode;
    private String resultMessage;
    private T data;
}

但是,我公司的结构大概是这样子的:

{
 "code": 0,
 "content": "成功",
}

你们可能会问,怎么少了一个。

其实不是少了一个,而是两个融合在一起了。。。。

那就尴尬了。然后去找后台大哥商讨,后台大哥说,我们公司的框架就是这样子的,改不了。。。。。

看来就只能我这边自己搞了。

其实也不难就是繁琐了一点,就多了一个步骤。

一般的处理步骤可以参考一下上面 推荐的文章 。

然后数据模型,就大概是这样子了。

BaseModel

public class BaseModel {
    private int code;
    private String content;
}

接口写起来就简单多了。

APIService

public interface APIService {
    /**
     * 登录
     */
    @FormUrlEncoded
    @POST("/login/checkLogin")
    Observable<BaseModel> login(@Field("userNumber") String userNumber);
}

以前的时候,为了方便,我就直接把 参数 和 Observer 都传进来,在内部直接就 Observable 和 observer 关联起来了。

/**
 * 登录
 */
public static void login(String userCode,String passwd,Observer<String> observer){
    Observable<BaseModel> Observable = getKaoQinAPI().signIn( userCode,name, passwd);
    observable.subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}

用起来也是很爽。

APIMethods.login("10086","10010",new new Observer<String>(){

                    @Override
                    public void onSubscribe(Disposable d) {
                        //开始
                    }

                    @Override
                    public void onNext(String value) {
                        //接口返回数据
                    }

                    @Override
                    public void onError(Throwable e) {
                        //出错
                    }

                    @Override
                    public void onComplete() {
                        //完成
                    }
                });

真的是一行代码,就可以了。

但是,这样写有一个很明显的问题,有时候要连续两次调用接口或者几个连续的操作。比如,要上传几张,还有一段文本,还有,一些用户信息。然后再上传图片前还要进行压缩。又或者发送请求后接收数据,然后对数据进行处理,然后再进行 IO 操作,最后要显示到界面上。

那么问题来了,按照之前的写法,就很被动了,这个过程就要被拆分开来写,或者进行嵌套。

那就违背了 RxJava 的设计初衷了,既然用了 RxJava 就要用到底。RxJava 就是为了解决这些复杂的操作,把这些操作转化成链式的步骤。那么逻辑就清晰很多了。

那就不能像上面那样写了。而是要把 Observer 返回出去,一旦拿到 Observer 再配合 RxJava 的各种操作符,就可以把整个过程串联起来了。

/**
 * 登录
 *
 * @param userCode 用户账号
 */
public static Observable<BaseModel> login(String userCode) {
    return getAPIService()
            .login(userCode);
}

但是有一点不爽,就是我们实际要使用的数据其实就只有 content 的这个字段而已,如果我们每次都在要用到的时候都要重新把数据拆分开,那就很麻烦了,这个时候就用到 RxJava 的 map 接口了。

map

这个接口可以把一种 数据类型的 Observable 转化成 另一种数据类型的 Observable ,我们就可以把 BaseModel 转化为 Content(其实就是 String 类型)

PreFunction

/**
 * 对数据进行预先处理
 */
public class PreFunction implements Function<BaseModel, String> {
    @Override
    public String apply(BaseModel baseModel) throws Exception {
        return baseModel.getContent();
    }
}

但是又没这么简单,因为,如果请求出现问题。比如,可能服务器异常session 过期 等等的情况,这个时候取出 content 就有问题了。

所以我们要先对返回的数据进行筛选和处理。现在 RxJava 2 是支持在 Function 里面直接抛出异常的,然后会在 onError 里面捕获到。

先定义一种异常,用来标识服务器返回的异常。

ApiException

public class ApiException extends RuntimeException {

    //服务器异常 ---对应 code == 4
    public static final int SERVER_EXCEPTION = 4;
    //令牌过期 或 在其他客户端登录 --- 对应 code ==3
    public static final int SESSION_EXCEPTION = 3;
    private static String exception;
    public int errorCode=-1;
    public static String getException() {
        return exception;
    }

    public ApiException(int resultCode) {
        this(getExceptionMessage(resultCode));
        errorCode=resultCode;
    }
    public ApiException(int resultCode, String message){
        this(message);
        errorCode=resultCode;
    }
    /**
     * 由于服务器传递过来的错误信息直接给用户看的话,用户未必能够理解
     * 需要根据错误码对错误信息进行一个转换,再显示给用户
     */
    public static String getExceptionMessage(int code){
        exception = "未知异常";
        switch (code){
            case SERVER_EXCEPTION:
                exception ="服务器异常";
                break;
            case SESSION_EXCEPTION:
                exception ="令牌过期 或 账号异地登录";
                break;
        }
        return exception;
    }

    public ApiException(String detailMessage) {
        super(detailMessage);
        exception=detailMessage;
    }


}

预处理就可以这么写

/**
 * 对数据进行预先处理
 */
public class PreFunction implements Function<BaseModel, String> {
    @Override
    public String apply(BaseModel baseModel) throws Exception {
        if (baseModel.getCode() == 2) {
        //code == 2 的时候要输出信息给用户
            throw new ApiException(baseModel.getCode(),baseModel.getContent());
        } else if (baseModel.getCode() != 1) {
        //code != 1  是说明请求出错
            throw new ApiException(baseModel.getCode());
        }
        return baseModel.getContent();
    }
}

然后,返回的 Observer 就可以这么写

/**
 * 登录
 *
 * @param userCode 关员号
 */
public static Observable<String> login(String userCode) {
    return getAPIService()
            .login(userCode)
            .map(new PreFunction());
}

这样就可以直接拿到 content 了。

Observable 和 Observer 之间的订阅 ,其实也都是差不多的,也可以提取出来

    public static <T> void toSubscribe(Observable<T> observable, Observer<T> observer) {
        observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }

使用起来是这样的

Observable<String> observable = APIMethods.login(User_Code);
Observer<String> observer = new Observer<String>() {

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(String value) {
        //登录成功
    }

    @Override
    public void onError(Throwable e) {
        //出错
    }

    @Override
    public void onComplete() {

    }
};
APIMethods.toSubscribe(observable,observer);

用起来也可以,但是就是 接口里面的方法 有好几个是我们不需要的,又占位置,又看起来不舒服,改!

我的想法是写一个类,实现接口的所有方法,然后用的时候,需要哪个接口,重写就行了。

HttpResultObserver

public class HttpResultObserver<T> implements Observer<T> {
    //用来取消请求
    public Disposable disposable;
    @Override
    public void onSubscribe(Disposable d) {
        disposable=d;
    }

    @Override
    public void onNext(T value) {

    }

    @Override
    public void onError(Throwable e) {
        //如果是我们自己定义的异常,就转化成描述后,显示给用户看
        if (e instanceof ApiException) {
            String exception = ((ApiException) e).getException();
            Toast.makeText(MyApplication.getINSTANCE(), exception, Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onComplete() {

    }
}

嗯,到这来就差不多了。

还有一个需求,就是发送网络请求时需要有一个加载框,然后网络请求结束后,就关闭加载框。

在推荐的文章中是使用 Handler 来做。虽然说不上来哪里不好,但是总感觉怪怪的。而且就像前面我以前犯的错误类似,就把加载框和网络请求绑死了。要是要和其他异步操作衔接就比较麻烦了。

我是用接口来做的。

先定义一个接口

DialogDisplay

public interface DialogDisplay {
    //显示 dialog
    void showDialog(DialogCancelListener dialogCancelListener,boolean canCancelable);
    //关掉 dialog
    void dismissDialog();
}

然后在 BaseActivity 中实现这个接口

@Override
public void showDialog(final DialogCancelListener dialogCancelListener,boolean canCancelable) {
    if(loadingDialog ==null){
        loadingDialog = new ProgressDialog(this);
        loadingDialog.setMessage("正在加载中。。。");
    }
    loadingDialog.setCancelable(canCancelable);
    if (canCancelable){
        loadingDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                if (dialogCancelListener!=null){
                    dialogCancelListener.onCancelProgress();
                }
            }
        });
    }
    if (!loadingDialog.isShowing()){
        loadingDialog.show();
    }
}

@Override
public void dismissDialog() {
    if (loadingDialog !=null){
        loadingDialog.dismiss();
        loadingDialog =null;
    }
}

然后把 BaseActivity 传入 observer ,让 observer 来控制 加载框。

既然要封装就要彻底。

写两个接口

OnSuccessListener

public interface OnSuccessListener<T> {
    void onSuccess(T result);
}

OnFailureListener

public interface OnFailureListener {
    void onFailure(Throwable throwable);
}

然后写个抽象类,实现这两个接口,但是不实现 OnSuccess 方法,使用的时候就必须实现这个方法,就不用,每次去手动重写。(Android Studio 会自动填充 抽象方法)

还有就是 加载框 可否取消,取消后要进行什么操作。

嗯,也用接口来做

DialogCancelListener

public interface DialogCancelListener {
    void onCancelProgress();
}

搞完之后大概就这样子了

HttpProgressDialogObserver

public abstract class HttpProgressDialogObserver<T> extends HttpResultObserver<T>
        implements DialogCancelListener,OnSuccessListener<T>,OnFailureListener{

    private final DialogDisplay dialogDisplay;
    private boolean canCancelable;

    public HttpProgressDialogObserver(DialogDisplay dialogDisplay,boolean canCancelable) {
        this.dialogDisplay =dialogDisplay;
        this.canCancelable=canCancelable;
    }

    @Override
    public void onSubscribe(Disposable d) {
        super.onSubscribe(d);
        if (dialogDisplay!=null){
            dialogDisplay.showDialog(this,canCancelable);
        }
    }

    @Override
    public void onNext(T value) {
        super.onNext(value);
        onSuccess(value);
    }

    @Override
    public void onComplete() {
        super.onComplete();
        if (dialogDisplay!=null){
            dialogDisplay.dismissDialog();
        }
    }

    @Override
    public void onError(Throwable e) {
        super.onError(e);
        if (dialogDisplay!=null){
            dialogDisplay.dismissDialog();
        }
        onFailure(e);
    }

    @Override
    public void onCancelProgress() {
        if (disposable!=null){
            if (!disposable.isDisposed()){
                disposable.dispose();
            }
        }
    }

    @Override
    public void onFailure(Throwable throwable) {

    }
}

用起来大概是这样子的

Observable<String> login = APIMethods.login(User_Code);
HttpProgressDialogObserver<String> loginObserver = new HttpProgressDialogObserver<String>(LoginActivity.this, false) {

    @Override
    public void onSuccess(String result) {
        
    }

};
APIMethods.toSubscribe(login,loginObserver);

今天在整理之前的笔记的时候,发现这个已经有点旧了,后面在这个基础上改进一些方法,后面又时间在写一篇文章吧。这里就补充一个点。

RxJava 的链式结构呢?

用过 RxJava 的同学应该都知道,RxJava 的链式结构,让人能够对发生的事情的顺序一目了然,很清晰。但是,我们上面的这个结构很明显被切分成了三段,这样就破坏了 RxJava 优雅的链式结构,这能忍?

之前没有找到好的解决方法之前,也就忍了,后面在看到一些项目使用到了一个 ObservableTransformer 的东西,那时候就在想,“卧槽,这不就是我要找的东西嘛!” 。真是相见恨晚,查了一下资料后马上就用上了。

代码大概是这样:

先创建一个 ObservableTransformer

Transformer实际上就是一个Func1<Observable, Observable>,换言之就是:可以通过它将一种类型的Observable转换成另一种类型的Observable,和调用一系列的内联操作符是一模一样的。

public static ObservableTransformer<BaseModel,String> io_main(){
    return new ObservableTransformer<BaseModel, String>() {
        @Override
        public ObservableSource<String> apply(Observable<BaseModel> upstream) {
            return upstream.subscribeOn(Schedulers.io())
                    .unsubscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .map(new PreFunction());
        }
    };
}

然后使用 compose 操作符,进行处理。

compose()是唯一一个能够从数据流中得到原始Observable的操作符,所以,那些需要对整个数据流产生作用的操作(比如,subscribeOn()和observeOn())需要使用compose()来实现。

APIMethods.login(User_Code)
        .compose(APIMethods.io_main())
        .subscribe(
            new HttpProgressDialogObserver<String>(LoginActivity.this, false) {
        
            @Override
            public void onSuccess(String result) {
                
            }
        
        });

使用起来就和上面看到的一样,是如此的简洁、清晰。这真的是太棒了!

参考文章:【译】避免打断链式结构:使用.compose( )操作符

拜拜

讲完了,拜拜。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×